--╔══════════════════════════════╗════════════════════════════════════════════════════════════════════════════════════╗
--║     Thinkframe Functions     ║ Thinkframe, PreThinkFrame, and PostThinkFrame, since order of execution matters!
--╚══════════════════════════════╝════════════════════════════════════════════════════════════════════════════════════╝
local RAIL = GS_RAILS if RAIL.blockload return end
local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end

---Use PreThinkframe for menu stuff and to prevent jank interactions!
RAIL.PreThinkFrame = function(_)
	if (gamestate!=GS_LEVEL) return end
	
	for p in players.iterate
		if not (p and p.valid) continue end local s = p.mo if not (s and s.valid) continue end 
		local GS = s.GSgrind	
		
		if s.GSRailMenu
			RAIL.MenuThinker(p, s, s.GSRailMenu) continue --The rest doesn't matter rn.
		end 
		if GS
		/*	if (leveltime%71==0) and not (p.bot)
				if GS.playerskin!=s.skin and p.GSRO and s.skin!=p.GSRO.playerskin and p.GSRO.playerskin!=nil
					p.GSRO.hasloaded = nil
					RAIL.SaveLoad(p, false, true, nil, s.skin)
				end
				GS.playerskin = s.skin 
			end */
			if GS.forcemove --Forced analog input for a specified amount of time.
				GS.forcemove.timer = $-1
				if GS.forcemove.forwardmove!=nil
					p.cmd.forwardmove = GS.forcemove.forwardmove
				end
				if GS.forcemove.sidemove!=nil
					p.cmd.sidemove = GS.forcemove.sidemove
				end				
				if not GS.forcemove.timer
					GS.forcemove = nil
				end
			end
			
			--Chasecam settings. Best to do this as early as possible.
			if (netgame!=true)
				if p==players[1] and splitscreen
					if not (camera2) or camera2.chase
						GS.chasecam = true
					else
						GS.chasecam = false
					end
				else
					if not (camera) or camera.chase
						GS.chasecam = true
					else
						GS.chasecam = false
					end
				end
			elseif p==displayplayer and p==consoleplayer and p.GSRO and p.GSRO.hasloaded --Sync cam status.
				if not (camera) or camera.chase
					if GS.chasecam!=true
						COM_BufInsertText(p, "GSRailMenu RL_CHASEON") GS.chasecam = true 
					end
				elseif GS.chasecam!=false
					COM_BufInsertText(p, "GSRailMenu RL_CHASEOFF") GS.chasecam = false
				end
			end
			if GS.grinding and GS.myrail and GS.myrail.valid
				s.friction = FRACUNIT
				if not GS.leftrail 		
					GS.side = p.cmd.sidemove or 0
					GS.forward = p.cmd.forwardmove or 0
					if p.panim==PA_IDLE p.panim = PA_ETC end --Never allow that panim
					s.eflags = $ & ~(MFE_JUSTHITFLOOR|MFE_APPLYPMOMZ|MFE_ONGROUND)
					p.pflags = $ & ~(PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SPINNING|PF_STARTDASH|PF_SLIDING|PF_SHIELDABILITY)
					p.speed = max(1, abs(GS.railspeed or 0))
					if (GS.myrail.GSgrindflags & 131072) --Don't jump out of the cinematic "nomove" part.
						GS.railjump = 99<<16
						p.pflags = $|PF_JUMPDOWN
					end
					if GS.myrail.GSwalljumprail
						p.cmd.sidemove,p.cmd.forwardmove = 0,0
					end
				end
			elseif GS.railswitching 
				if (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE|PF_BOUNCING)) 
					if (GS.railswitching > 2) and not P_IsObjectOnGround(s)
						if (GS.railswitching > 10)
							p.cmd.sidemove = 0
						elseif (abs(p.cmd.sidemove) > 3)
							p.cmd.sidemove = $/3						
						end
						if (abs(p.cmd.forwardmove) > 3)
							p.cmd.forwardmove = $/3
						end
						p.cmd.buttons = $ & ~(BT_CAMLEFT|BT_CAMRIGHT)
						p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~(PF_STARTJUMP|PF_SPINNING|PF_SHIELDABILITY)
					else
						p.pflags = $ & ~PF_SHIELDABILITY
					end
				end
			elseif p.homing and s.tracer --Immediately unstuck "Cannotgrind value"
				if (s.tracer.GShomingtarget) or s.tracer.railobject
					GS.cannotgrind,p.homing = 0, min(p.homing, 40)
				end
			end
			if GS.walljump and GS.walljump.timer --Gradually ease back into full control
				if GS.walljump.loopcam
					if (GS.walljump.init < 52)
						if GS.chasecam==false p.cmd.sidemove = 0 end
						p.cmd.forwardmove = 0
					elseif (GS.walljump.init < 60)
						if GS.chasecam==false p.cmd.sidemove = $/(60-GS.walljump.init) end
						p.cmd.forwardmove = $/(60-GS.walljump.init)
					end
				end
				if (GS.walljump.init < 8)
					local DIV = max(1, 8-GS.walljump.init)
					p.cmd.forwardmove,p.cmd.sidemove = $/DIV,$/DIV
					if (GS.walljump.init < 5)
						p.pflags = $|PF_JUMPDOWN|PF_USEDOWN
					end
				end
			elseif ( GS.grinding or ((GS.leftrail or 0) > 1) ) and (P_IsObjectOnGround(s) or (s.eflags & MFE_JUSTHITFLOOR))
				if not p.cmd.sidemove and not p.cmd.forwardmove and not (p.pflags & PF_SPINNING)
					if GS.LastRail and GS.LastRail.GSloopflags and (GS.LastRail.GSloopflags & (262144|524288))
						p.cmd.sidemove = (s.momy>0) and 1 or -1
					else
						p.cmd.forwardmove = 1 --SRB2 will forcibly stop all momentum otherwise...
					end
				end
			end			
		end
		if p.WasInRailMenu --Reset the music volume.
			if p.WasInRailMenu==gamemap S_SetInternalMusicVolume(100, p) end
			p.pflags,p.WasInRailMenu = $ & ~PF_FORCESTRAFE, nil		
		end
	end
end

--Use Thinkframes for most other stuff, to ensure animations and cameras won't break easily!
RAIL.ThinkFrame = function(_)
	if (gamestate!=GS_LEVEL) return end
	for p in players.iterate
		if p and p.valid RAIL.PlayerThink(p) end
	end
end

--Use PostThinkframes for animations, cameras, and the director behaviour.
RAIL.PostThinkFrame = function(_)
	if (gamestate!=GS_LEVEL) return end --ALWAYS be in a level first.
	
	local DIRECTOR,WAIT = RAIL.DirectorSpawnOrThink(GS_RAILS_DIRECTOR, "PostThink")
	if WAIT==true and (leveltime < 3) 
		if ((RAIL.INTTIMER or 0) <= leveltime)
			RAIL.INTTIMER= leveltime
		end
		return --
	end

	for p in players.iterate
		if not (p and p.valid) continue end --
		local s = p.mo 
		
		if not (s and s.valid) continue end --
		local GS = s.GSgrind
		
		if not (GS) continue end --
		
		if GS.animcall or GS.forcesprite!=nil
			RAIL.CustomAnim(p, s, GS)
		end
		if GS.forceangle!=nil
			p.drawangle = GS.forceangle
			GS.forceangle = nil
		end
		if ((GS.railswitching or 0) > 2) 
			if (GS.switchCAMang!=nil) and not ((s.flags2 & MF2_TWOD) or twodlevel)
				p.cmd.angleturn = GS.switchCAMang
			end
		else
			if GS.grinding
				s.friction = FRACUNIT
				local rail = GS.myrail or GS.LastRail
				if rail and rail.valid
					local RSTATS = GS.MyRailStats
					if rail.GSVertRail
					or RSTATS and RSTATS.VERTRAIL
						if rail.GSnextrail and not (rail.GSloopflags & 8388608)
							if (s.sprite2 & ~SS2)==SPR2_ROLL
								s.rollangle = 0
								s.frame = $ & ~FF_VERTICALFLIP
							else
								local TOANG = RAIL.ANG(rail)+ANGLE_180 --R_PointToAngle2(rail.x, rail.y, rail.GSnextrail.x, rail.GSnextrail.y) 
								if (rail.GSnextrail.z < rail.z) TOANG = $+ANGLE_180 end
								local MYANG =  p.cmd.angleturn<<16
								if displayplayer and netgame==true and (p!=displayplayer) and displayplayer.valid
									if displayplayer.awayviewtics and displayplayer.awayviewmobj and displayplayer.awayviewmobj.valid
										MYANG = displayplayer.awayviewmobj.angle
									else
										MYANG = displayplayer.cmd.angleturn<<16
									end
								elseif p.awayviewtics and p.awayviewmobj and p.awayviewmobj.valid
									MYANG = p.awayviewmobj.angle
								end
								local ANGDIFF,PLUS = RAIL.AngleDifference(MYANG, TOANG, 2, ANGLE_90, 2)
								if (ANGDIFF >= ANG10) and not (ANGDIFF >  ANG1*85)
									PLUS = $*((s.mirrored) and -1 or 1) --*((ANGDIFF > ANG60) and -1 or 1)
								/*	if (s.frame & FF_HORIZONTALFLIP) PLUS = $*-1 end
									if (s.frame & FF_VERTICALFLIP) PLUS = $*-1 end */
									ANGDIFF = min(ANG2*26, max(ANG2*17, $))
									local LOOP = RAIL.LearnLoopType(rail) 
									if LOOP and LOOP.walljump
										local SKIN = GS_RAILS_SKINS[s.skin] or nil
										local STATS = (SKIN) and SKIN["S_SKIN"] or nil										
										if STATS and STATS.WALLCLINGSPRITE --Special sprite just for this? No rollangle, then.
											s.rollangle = 0
										else
											s.rollangle = (ANGDIFF/3)*PLUS
										end
									else
										s.rollangle = ANGDIFF*(PLUS*2)
									end
								--	print("FF_VFLIP:\x82"+(s.frame&FF_VERTICALFLIP)+"\x80 FF_HFLIP:\x82"+(s.frame&FF_HORIZONTALFLIP))
								--	print("s.rollangle:\x84"+s.rollangle/ANG1+"\x80 ANGDIFF:\x87"+ANGDIFF/ANG1+"\x80 s.mirrored:\x81"+s.mirrored+"\x80 PLUS:\x86"+PLUS)
								else
									s.rollangle = 0
								end
								s.frame = $ & ~FF_VERTICALFLIP
							end
							if not (rail.GSLoopType) and not (rail.GSloopflags & (262144|524288|4194304))
							and not (rail.GSgrindflags & 131072) and (GS.TWOD!=true) and RSTATS
							and (RSTATS.ZSPEED) and not p.awayviewtics
							and not ((GS.justtouchedrailthing or 0)>4) and not (GS.forceVaim) and GS.OGAIM==nil 
								GS.OGAIM = p.aiming --Auto cam on vertical rails?
								local ZSPEED = (RSTATS.ZSPEED/(s.scale/3))*ANG1
								if (RSTATS.ZSPEED > 0)
									p.aiming = min(ZSPEED, ANG2*29)
								else
									p.aiming = max(ZSPEED, -ANG2*29)
								end
							end
						end
					end
					if GS.railsparks
						local FOUND = false
						if RSTATS
							local VERTRAIL,ghs = RSTATS.VERTRAIL, nil
							for i = -1,1,2 --Adjust rail sparks to where they should be.
								ghs = GS.railsparks[i] if not (ghs and ghs.valid) continue end --
								FOUND = true
								if VERTRAIL
									if (abs(s.rollangle) > (ANG10*5))
									/*	ghs.frame = ($&~FF_HORIZONTALFLIP)|((GS.railspeed < 0) and FF_HORIZONTALFLIP or 0)
										ghs.rollangle = ((GS.MyRailStats.ZSPEED or 0) < 0) and ANGLE_90 or ANGLE_270
										ghs.VERTROLL = true */
										ghs.spritexscale,ghs.spriteyscale = s.spritexscale/64,s.spriteyscale/64
									--	ghs.flags2 = $|MF2_DONTDRAW
									--	P_RemoveMobj(ghs)
									else
										P_MoveOrigin(ghs, s.x, s.y, ghs.z)
									end
								else
									if ghs.VERTROLL==true
										ghs.VERTROLL = nil
										ghs.rollangle = 0
									end
									ghs.z = s.z+ ((s.eflags&MFE_VERTICALFLIP) and abs(s.height) or 0)	
								end
							end
						end
						if FOUND!=true GS.railsparks = nil end --Delet
					end
					if GS.railcam and GS.railcam.valid and not (GS.chasecam==false)
						if GS.railcam.PostANG=="face"
							GS.railcam.angle = R_PointToAngle2(GS.railcam.x, GS.railcam.y, s.x, s.y)
							GS.railcam.PostANG = nil
						end
					/*	if not ((rail.GSloopflags or 0) & 8388608)
							local CAM = GS.railcam
							if p.awayviewmobj and p.awayviewmobj.valid
								if (p.awayviewmobj!=GS.railcam)
									CAM = p.awayviewmobj
									P_MoveOrigin(GS.railcam, CAM.x, CAM.y, CAM.z)
									GS.railcam.angle = CAM.angle
									GS.railcam.momx,GS.railcam.momy, GS.railcam.momz = CAM.momx,CAM.momy,CAM.momz
								elseif p.solcam and mapheaderinfo[gamemap].solcam=="on"
									if p.solcam.mode=="default" and not p.solcam.activetimer and not p.solcam.transitionrem
										p.solcam = nil --Let it remake itself later.
									else
										if p.solcam.mobj and p.solcam.mobj.valid --Solcam, but not the cam we're using?
											local CAM = GS.railcam local SOL = p.solcam.mobj
											P_MoveOrigin(SOL, CAM.x, CAM.y, CAM.z) --Adjust it.
											SOL.angle = CAM.angle
										end 
									end
								end
							end
							if (netgame!=true) and p==consoleplayer and camera and camera.chase
								P_TeleportCameraMove(camera, CAM.x, CAM.y, CAM.z)
								camera.momx,camera.momy,camera.momz = CAM.momx,CAM.momy,CAM.momz
								camera.angle = CAM.angle
							end
						end */
					end
				end
			else
				if GS.JumpRamp and GS.JumpRamp.rollangle!=nil --Enforce rollangle strictly.
					s.rollangle = GS.JumpRamp.rollangle
				elseif GS.FuckingROLL --When gentle persuasion refuses to work...
					if not GS.cannotgrind
						GS.FuckingROLL = nil
					elseif not p.powers[pw_carry] or P_PlayerInPain(p)
						GS.FuckingROLL = max($-1,0)
						p.pflags = $|PF_SPINNING|PF_USEDOWN|PF_JUMPDOWN & ~PF_THOKKED
						s.state = S_PLAY_ROLL
						if not P_IsObjectOnGround(s)
							p.pflags = $|PF_JUMPED
						end
						if not GS.FuckingROLL
							GS.FuckingROLL = nil
						end
					end
				end
			end
			if GS.speedtrail --Do an adventure-style spintrail?
				RAIL.SpeedTrail(s, GS)
				if type(GS.speedtrail)=="number"
					GS.speedtrail = max($-1,0)
				else
					GS.speedtrail = nil
				end
			end					
			GS.LastXYZ = {X = s.x, Y = s.y, Z = s.z}	
		end	
		local SKIN = GS_RAILS_SKINS[s.skin]
		if SKIN and SKIN["FinalThinker"]
			SKIN["FinalThinker"](p, s, GS) --The final thinker that goes after EVERYTHING else.
		end
	end --End of Player Iterator.
	
	local VERSION = GS_RAILS_V
	if VERSION.timer
		VERSION.timer = max($-1,0)
		if not VERSION.timer
			if consoleplayer --Print a message on load!
				local TEXT,SRB2V = "\x86", "SRB2 "
				local VTEXT = VERSION.string if VTEXT==nil VTEXT = "???" end  --What to dub this version. Purely cosmetic.
			--	local VTEXT = "\x85"+"SATv0.92." --What to dub this version. Purely cosmetic.
				if ((SUBVERSION or 0) < 13) or type(VERSIONSTRING)!="string"
					SRB2V = $+"\x85"+"v2.2.13+"+"("+"\x89"+"This SRB2 version might not work!)"+"\x80"
				else
					SRB2V = $+"\x83"+VERSIONSTRING+"+"+"\x80"
				end
				if VERSION.FirstLoad==true
					TEXT = $+"═════════════════"+"\n"+"\x82"
					+"GS Grind Rails\x80 ["+VTEXT+GS_RAILS.version+"\x80"+"] for ["+SRB2V+"] by\x82 Golden Shine!"
				else
					TEXT = $+"═════════════════"+"\n"+"\x83"
					+"<═UPDATED═>"+"\x82 GS Grind Rails\x80 to ["+VTEXT+GS_RAILS.version+"\x80"+"]"
				end
				CONS_Printf(consoleplayer, TEXT) chatprintf(consoleplayer, TEXT, false)
			end
			VERSION.FirstLoad,VERSION.timer = nil 
		end
	end	
	if ((RAIL.INTTIMER or 0) <= leveltime)
		RAIL.INTTIMER = leveltime
	end
end